package tech.veedo.ragdoll.processor;

import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Nonnull;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import tech.veedo.ragdoll.entity.GlobalResponseAdvice;

import java.lang.reflect.Member;
import java.util.Arrays;

@RestControllerAdvice
public class GlobalResponseProcessor implements ResponseBodyAdvice<Object> {

    private String[] ignoreMethods;

    private String[] basePackages;

    /**
     * Whether this component supports the given controller method return type
     * and the selected {@code HttpMessageConverter} type.
     *
     * @param returnType    the return type
     * @param converterType the selected converter type
     * @return {@code true} if {@link #beforeBodyWrite} should be invoked;
     * {@code false} otherwise
     */
    @Override
    public boolean supports(MethodParameter returnType, @Nonnull Class converterType) {
        Member m = returnType.getMember();
        if (basePackages != null && basePackages.length > 0) {
            String classPath = m.getDeclaringClass().getPackage().getName();
            return Arrays.asList(this.basePackages).contains(classPath) && this.ignore(m);
        } else {
            return ignore(m);
        }
    }

    private boolean ignore(Member m) {
        if (ignoreMethods != null && ignoreMethods.length > 0) {
            String methodName = m.getName();
            return Arrays.stream(this.ignoreMethods).noneMatch((ignoreMethod) -> ignoreMethod.equals(methodName));
        } else {
            return true;
        }
    }

    /**
     * Invoked after an {@code HttpMessageConverter} is selected and just before
     * its write method is invoked.
     *
     * @param body                  the body to be written
     * @param returnType            the return type of the controller method
     * @param selectedContentType   the content type selected through content negotiation
     * @param selectedConverterType the converter type selected to write to the response
     * @param request               the current request
     * @param response              the current response
     * @return the body that was passed in or a modified (possibly new) instance
     */
    @Override
    public Object beforeBodyWrite(Object body,
                                  @Nonnull MethodParameter returnType,
                                  @Nonnull MediaType selectedContentType,
                                  @Nonnull Class selectedConverterType,
                                  @Nonnull ServerHttpRequest request,
                                  @Nonnull ServerHttpResponse response) {
        if (body instanceof GlobalResponseAdvice) {
            return body;
        } else if (body instanceof String) {
            return new GlobalResponseAdvice<String>()
                    .setData(StrUtil.isBlankIfStr(body)?null:String.valueOf(body));
        } else if (body instanceof Boolean) {
            return new GlobalResponseAdvice<Boolean>().setData(Boolean.valueOf(String.valueOf(body)));
        } else {
            return new GlobalResponseAdvice<>().setData(body);
        }
    }

    public String[] getIgnoreMethods() {
        return ignoreMethods;
    }

    public void setIgnoreMethods(String[] ignoreMethods) {
        this.ignoreMethods = ignoreMethods;
    }

    public String[] getBasePackages() {
        return basePackages;
    }

    public void setBasePackages(String[] basePackages) {
        this.basePackages = basePackages;
    }

}
